package gbench.common.matlib.img;

import static gbench.common.tree.LittleTree.IRecord.REC;
import static gbench.common.tree.LittleTree.Tuple2.TUP2;

import javax.imageio.ImageIO;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import gbench.common.tree.LittleTree.IRecord;
import gbench.common.tree.LittleTree.Tuple2;

/**
 * 图像常用操作
 * 
 * @author gbench
 *
 */
public class ImageOps {
    /**
     * 图片数据读取
     * 
     * @param imgfile 图片路径
     * @return rgbinfo 数据摘要 ,{width,height, rgbs:[x][y][r,g,b]}
     */
    public static IRecord img2rgbinfo(final String imgfile) {
        final var input = new File(imgfile);
        BufferedImage bimg = null;
        try {
            bimg = ImageIO.read(input);// 读取数据
        } catch (IOException e) {
            e.printStackTrace();
        } // 读取本地图片

        return img2rgbinfo(bimg);
    }

    /**
     * 图片数据读取
     * 
     * @param bimg 图片对象
     * @return rgbinfo 数据摘要 ,{width,height, rgbs:[x][y][r,g,b]}
     */
    public static IRecord img2rgbinfo(final BufferedImage bimg) {
        final var rgbinfo = REC("height", 0, "width", 0, "rgbs", new int[][][] {}); // 默认结果状态
        if (bimg == null)
            return rgbinfo;

        final var height = bimg.getHeight();
        final var width = bimg.getWidth();
        final var ras = bimg.getData();// 读取栅格数据
        final var rgbs = new int[width][height][3];// rgb 数据 数组[r,g,b]

        rgbinfo.add(REC("width", width, "height", height, "ras", ras, "rgbs", rgbs)); //
        for (int y = 0; y < height; y++) {// y 高度
            for (int x = 0; x < width; x++) { // x 水平
                ras.getPixel(x, y, rgbs[x][y]);// 读取像素数据
            } // x
        } // for y

        return rgbinfo;
    }

    /**
     * 颜色向量空间
     * 
     * @param imgfile 图片路径
     * @return 颜色向量空间
     */
    public static List<int[]> img2xyrgbs(final String imgfile) {
        final var rgbinfo = img2rgbinfo(imgfile);
        return rgbinfo2xyrgbs(rgbinfo);
    }

    /**
     * 颜色向量空间
     * 
     * @param bimg 图片路径
     * @return 颜色向量空间
     */
    public static List<int[]> img2xyrgbs(final BufferedImage bimg) {
        final var rgbinfo = img2rgbinfo(bimg);
        return rgbinfo2xyrgbs(rgbinfo);
    }

    /**
     * 生成颜色向量空间
     * 
     * @param rgbinfo 颜色数据 {width,height, rgbs:[[r,g,b]]}
     * @return 颜色向量空间 [[x,y, r,g,b]]
     */
    public static List<int[]> rgbinfo2xyrgbs(final IRecord rgbinfo) {

        return rgbinfo2xyrgbL(rgbinfo, xyrgb -> xyrgb);
    }

    /**
     * 生成颜色向量空间
     * 
     * @param <T>               xyrgb_transformer 的变换结果类型
     * @param rgbinfo           颜色数据 {width,height, rgbs:[[r,g,b]]}
     * @param xyrgb_transformer xyrgb的变换器:xyrgb->T <br>
     *                          xyrgb是一个像素点的数组/向量 <br>
     *                          [x:像素的水平索引位置,y:像素的垂直索引位置,r:绿色分量,b:蓝色分量]
     * @return 颜色向量空间的列表 [[x,y, r,g,b]]
     */
    public static <T> List<T> rgbinfo2xyrgbL(final IRecord rgbinfo, final Function<int[], T> xyrgb_transformer) {

        return rgbinfo2xyrgbS(rgbinfo, xyrgb_transformer).collect(Collectors.toList());
    }

    /**
     * 生成颜色向量空间的数据流
     * 
     * @param <T>               xyrgb_transformer 的变换结果类型
     * @param rgbinfo           颜色数据 {width,height, rgbs:[[r,g,b]]}
     * @param xyrgb_transformer xyrgb的变换器:xyrgb->T <br>
     *                          xyrgb是一个像素点的数组/向量 <br>
     *                          [x:像素的水平索引位置,y:像素的垂直索引位置,r:绿色分量,b:蓝色分量]
     * @return 颜色向量空间的流 [[x,y, r,g,b]]
     */
    public static <T> Stream<T> rgbinfo2xyrgbS(final IRecord rgbinfo, final Function<int[], T> xyrgb_transformer) {

        final var height = rgbinfo.i4("height"); // 图片高度
        final var width = rgbinfo.i4("width"); // 图片宽度
        final var rgbs = (int[][][]) rgbinfo.get("rgbs"); // 颜色空间

        return Stream.iterate(0, y -> y < height, y -> y + 1).flatMap(y -> Stream.iterate(0, x -> x < width, x -> x + 1)
                .map(x -> xyrgb_transformer.apply(new int[] { x, y, rgbs[x][y][0], rgbs[x][y][1], rgbs[x][y][2] })));
    }

    /**
     * rgb 数据颜色转 图片数据。
     * 
     * @param width  图片宽度
     * @param height 图片高度
     * @param xyrgbs 色彩空间 (x：水平坐标从0开始,y:列坐标从0开始,color,颜色数据),
     * @return BufferedImage 图像数据。
     */
    public static BufferedImage xyrgbs2bimg(final int width, final int height, final List<int[]> xyrgbs) {
        final var bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        return render(bufferedImage, xyrgbs.stream(), 2);
    }

    /**
     * rgb 数据颜色转 图片数据。
     * 
     * @param width  图片宽度
     * @param height 图片高度
     * @param xyrgbs 色彩空间 (x：水平坐标从0开始,y:列坐标从0开始,color,颜色数据),
     * @return BufferedImage 图像数据。
     */
    public static BufferedImage xyrgbs2bimg(final int width, final int height, final Stream<int[]> xyrgbs) {
        final var bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        return render(bufferedImage, xyrgbs, 2);
    }

    /**
     * rgb 数据颜色转 图片数据。
     * 
     * @param width  图片宽度
     * @param height 图片高度
     * @param xyrgbs 色彩空间 (x：水平坐标从0开始,y:列坐标从0开始,color,颜色数据),
     * @return BufferedImage 图像数据。
     */
    public static BufferedImage xyrgbs2bimg(final Number width, final Number height, final List<Integer[]> xyrgbs) {
        final var bufferedImage = new BufferedImage(width.intValue(), height.intValue(), BufferedImage.TYPE_INT_RGB);
        return render(bufferedImage, unbox(xyrgbs).stream(), 2);
    }

    /**
     * rgb 数据颜色转 图片数据。
     * 
     * @param width  图片宽度
     * @param height 图片高度
     * @param xyrgbs 色彩空间 (x：水平坐标从0开始,y:列坐标从0开始,color,颜色数据),
     * @return BufferedImage 图像数据。
     */
    public static BufferedImage xyrgbs2bimg(final Number width, final Number height, final Stream<Integer[]> xyrgbs) {
        final var bufferedImage = new BufferedImage(width.intValue(), height.intValue(), BufferedImage.TYPE_INT_RGB);
        return render(bufferedImage, unbox(xyrgbs).stream(), 2);
    }

    /**
     * rgb 数据颜色转 图片数据。
     * 
     * @param rgbs 三维色彩空间矩阵 [x][y][r,g,b]
     * @return BufferedImage
     */
    public static BufferedImage rgbs2bimg(final int[][][] rgbs) {
        final int width = rgbs.length;
        final int height = rgbs[0].length;
        final var bimg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        final var ras = bimg.getRaster();
        for (int y = 0; y < height; y++) {// y
            for (int x = 0; x < width; x++) { // x
                ras.setPixel(x, y, rgbs[x][y]);
            } // for x
        } // for y
        return bimg;
    }

    /**
     * 使用xyrgbs 描绘 bufferedImage
     * 
     * @param bufferedImage 内存图片
     * @param xyrgbs        色彩向量组
     * @param start         色彩开始位置偏移
     * @return BufferedImage
     */
    public static BufferedImage render(final BufferedImage bufferedImage, final List<int[]> xyrgbs, final int start) {
        return render(bufferedImage, xyrgbs.stream(), start);
    }

    /**
     * 使用xyrgbs 描绘 bufferedImage
     * 
     * @param bufferedImage 内存图片
     * @param vectors       色彩向量组 [x,y,...,r,g,b,...]
     * @param start         色彩开始位置偏移
     * @return BufferedImage
     */
    public static BufferedImage render(final BufferedImage bufferedImage, final Stream<int[]> vectors,final int start) {
        final var ar = new AtomicReference<BufferedImage>(); // BufferedImage的引用对象
        final Runnable renderer = () -> ar.set(fast_render(bufferedImage, vectors, start)); // 请在此处指定 具体的 render 版本: 比如 fast_render 
        if (debug) gbench.common.tree.LittleTree.timeit(renderer); // 调试模式 启动计时
        else renderer.run(); // 普通模式
        return ar.get(); // 返回bufferedImage对象
    }

    /**
     * 使用xyrgbs 描绘 bufferedImage
     * 
     * @param bufferedImage 内存图片
     * @param vectors       色彩向量组 [x,y,...,r,g,b,...]
     * @param start         色彩开始位置偏移，这个参数对于fast_render 无效，可以设置为任何值，存在是为了与 slow_render进行统一
     * @return BufferedImage
     */
    public static BufferedImage fast_render(final BufferedImage bufferedImage, final Stream<int[]> vectors, final int start) {
        final var ras = bufferedImage.getRaster();// 获取网状栅格
        final var scanlineStride = ras.getWidth(); // 行像素数
        final var pixels = ((DataBufferInt) ras.getDataBuffer()).getData(); // 颜色数据数组:int[]

        vectors.forEach(vec -> { // 向量分解处理
            try {// 设置栅格像素
                final var x = vec[0]; // 水平分量
                final var y = vec[1]; // 垂直分量
                final var rgb = (vec[2] << 16) | (vec[3] << 8) | (vec[4]); // rgb 像素
                pixels[y * scanlineStride + x] = rgb; // rgb 颜色设置
            } catch (Exception e) {// 数据溢出
                System.err.println("像素:" + box2list(vec) + " 设置失败！");
            } // try
        });// forEach

        return bufferedImage;
    }

    /**
     * 使用xyrgbs 描绘 bufferedImage
     * 
     * @param bufferedImage 内存图片
     * @param vectors       色彩向量组 [x,y,...,r,g,b,...]
     * @param start         色彩开始位置偏移
     * @return BufferedImage
     */
    public static BufferedImage slow_render(final BufferedImage bufferedImage, final Stream<int[]> vectors,
            final int start) {
        final var ras = bufferedImage.getRaster();// 获取网状栅格
        split_handle(tup -> { // 向量分解处理
            final var index = tup._1(); // 向量坐标索引信息
            final var rgb = tup._2(); // 向量值信息
            try {// 设置栅格像素
                ras.setPixel(index[0], index[1], rgb); // 设置色彩值
            } catch (Exception e) {// 数据溢出
                System.err.println("索引位置(" + box2list(index) + ") 设置 (" + box2list(rgb) + ") 失败！");
            } // try
        }, vectors, start);

        return bufferedImage;
    }

    /**
     * 向量的分解&处理
     * 
     * @param handler (index,value)->{} // 把向量分解成 index和数值的二元组 进行处理。
     * @param vectors 值向量的处理
     * @param start   色彩开始位置偏移
     */
    public static void split_handle(final Consumer<Tuple2<int[], int[]>> handler, final Stream<int[]> vectors,
            final int start) {
        // 向量分解&处理
        vectors.forEach(v -> { // [x,y,...,r,g,b,...]
            final var index = Arrays.copyOfRange(v, 0, start);// 提取坐标 数据
            final var value = Arrays.copyOfRange(v, start, v.length);// 提取数值数据
            handler.accept(TUP2(index, value));
        });// forEach
    }

    /**
     * 数据装箱 int[] -> Integer[]
     * 
     * @param aa 基础类型的数组
     * @return 整形数组转整形对象数组。
     */
    public static List<Integer[]> box(List<int[]> aa) {
        return aa.stream().map(e -> Arrays.stream(e).boxed().toArray(Integer[]::new)).collect(Collectors.toList());
    }

    /**
     * 数据装箱 Integer[] -> int[]
     * 
     * @param aa 基础类型的数组
     * @return 整形数组转整形对象数组。
     */
    public static List<int[]> unbox(List<Integer[]> aa) {
        return aa.stream().map(e -> Arrays.stream(e).mapToInt(p -> p).toArray()).collect(Collectors.toList());
    }

    /**
     * 数据装箱 Integer[] -> int[]
     * 
     * @param stream 基础类型的数组
     * @return 整形数组转整形对象数组。
     */
    public static List<int[]> unbox(Stream<Integer[]> stream) {
        return stream.map(e -> Arrays.stream(e).mapToInt(p -> p).toArray()).collect(Collectors.toList());
    }

    /**
     * 把一个int[]转换成一个List
     * 
     * @param aa int[]类型的对象
     * @return 整形元素构成的队列
     */
    public static List<Integer> box2list(int[] aa) {
        return Arrays.stream(aa).boxed().collect(Collectors.toList());
    }

    /**
     * 判断数值是否位于start和end之间 <br>
     * x>=start&&x<end
     * 
     * @param start 起始值 inclusive
     * @param end   终止值 exclusive
     * @return 区间判断函数
     */
    public static Predicate<Number> between(final Number start, final Number end) {
        return x -> x.doubleValue() >= start.doubleValue() && x.doubleValue() < end.doubleValue();
    }

    /**
     * 写出一张图片。
     * 
     * @param rgbs 颜色数组
     * @param path 图片数据路径
     */
    public static BufferedImage jpgwrite(final int[][][] rgbs, final String path) {
        return imgwrite(rgbs2bimg(rgbs), path, DEFAULT_FORMAT_NAME);
    }

    /**
     * 写出一张图片。
     * 
     * @param bufferedImage 颜色数组
     * @param path          图片数据路径
     */
    public static BufferedImage jpgwrite(final BufferedImage bufferedImage, final String path) {
        return imgwrite(bufferedImage, path, DEFAULT_FORMAT_NAME);
    }

    /**
     * 写出一张图片。
     * 
     * @param bufferedImage 颜色数组
     * @param imgfile       图片数据路径
     * @param bgcolor       背景颜色
     */
    public static BufferedImage jpgwrite(final BufferedImage bufferedImage, final String imgfile, final Color bgcolor) {
        return imgwrite(bufferedImage, imgfile, DEFAULT_FORMAT_NAME, bgcolor);
    }

    /**
     * 写出一张图片。
     * 
     * @param bufferedImage 颜色数组
     * @param imgfile       图片数据路径
     * @param formatName    图片格式,null 默认为jpg
     */
    public static BufferedImage imgwrite(final BufferedImage bufferedImage, final String imgfile,
            final String formatName) {
        return imgwrite(bufferedImage, imgfile, formatName, (Color) null);
    }

    /**
     * 写出一张图片。
     * 
     * @param bufferedImage 颜色数组
     * @param imgfile       图片数据路径
     * @param formatName    图片格式,null 默认为jpg
     * @param bgcolor       背景颜色
     */
    public static BufferedImage imgwrite(final BufferedImage bufferedImage, final String imgfile,
            final String formatName, final int[] bgcolor) {
        return imgwrite(bufferedImage, imgfile, formatName, rgb2clr(bgcolor));
    }

    /**
     * 写出一张图片。
     * 
     * @param bufferedImage 颜色数组
     * @param imgfile       图片数据路径
     * @param formatName    图片格式,null 默认为jpg
     * @param bgcolor       背景颜色
     */
    public static BufferedImage imgwrite(final BufferedImage bufferedImage, final String imgfile,
            final String formatName, final Color bgcolor) {
        try {
            final var g = bufferedImage.createGraphics();// 绘图图片对象
            // g.setBackground(bgcolor);
            g.drawImage(bufferedImage, 0, 0, bgcolor == null ? Color.WHITE : bgcolor, null); // 背景填充色设置为白色
            final var file = new File(imgfile);
            ImageIO.write(bufferedImage, formatName == null ? DEFAULT_FORMAT_NAME : formatName, file);
        } catch (Exception e) {
            e.printStackTrace();
        } // try
        return bufferedImage;
    }

    /**
     * 读取图片生成颜色向量空间的数据流
     * 
     * @param <T>               xyrgb_transformer 的变换结果类型
     * @param imagefile         图片路径
     * @param xyrgb_transformer xyrgb的变换器:xyrgb->T <br>
     *                          xyrgb是一个像素点的数组/向量 <br>
     *                          [x:像素的水平索引位置,y:像素的垂直索引位置,r:绿色分量,b:蓝色分量]
     * @return 颜色向量空间的流 [[x,y, r,g,b]]
     */
    public static <T> Stream<T> imgread(final String imagefile, final Function<int[], T> xyrgb_transformer) {

        return rgbinfo2xyrgbS(img2rgbinfo(imagefile), xyrgb_transformer);
    }

    /**
     * rgb数组转颜色
     * 
     * @param rgb rgb数组
     * @return 颜色对象
     */
    public static Color rgb2clr(final int[] rgb) {
        if (rgb == null)
            return Color.black;
        return new Color(rgb[0], rgb[1], rgb[2]);
    }

    /**
     * rgb数组转颜色
     * 
     * @param rgbstr rgb的颜色的字符串表示，例如"#ff0000"表示红色
     * @return 颜色对象
     */
    public static Color rgb2clr(String rgbstr) {
        if (rgbstr == null)
            return Color.black;
        return rgb2clr(rgb(rgbstr));
    }

    /**
     * 随机的颜色
     * 
     * @return 随机rgb颜色数组
     */
    public static int[] rndrgb() {
        return new int[] { rand.nextInt(256), rand.nextInt(256), rand.nextInt(256) };
    }

    /**
     * 生成颜色
     * 
     * @param r 红色分量
     * @param g 绿色分量
     * @param b 蓝色分量
     * @return rgb的颜色数组
     */
    public static int[] rgb(final Number r, final Number g, final Number b) {
        return new int[] { r.intValue() % 256, g.intValue() % 256, b.intValue() % 256 };
    }

    /**
     * 把字符串形式的颜色值转换成 rgb数组 [r,g,b]
     * 
     * @param color 颜色向量，例如 "#ff0000"表示 [255,255,0,0]即红色
     * @return rgb的颜色数组
     */
    public static int[] rgb(final String color) {
        final var defaultclr = new int[] { 255, 0, 0 }; // 默认颜色
        if (color == null)
            return defaultclr;

        final var clr = color.toLowerCase();
        final var matcher = Pattern.compile("^#?\\s*([0-9a-f]{1,2})\\s*([0-9a-f]{1,2})\\s*([0-9a-f]{1,2})\\s*$")
                .matcher(clr.strip());
        final var parse = (Function<String, Integer>) digits -> {
            final var aa = digits.toCharArray();
            var value = 0;
            for (final var a : aa) {
                final var v = (a >= '0' && a <= '9') ? a - '0' : ((a >= 'a' && a <= 'f') ? a - 'a' : 0) + 10;
                value = (value != 0 ? value * 16 : 0) + v;
            } // for
            return aa.length < 2 ? value * 15 : value;
        }; // 16进制的解析函数

        if (matcher.matches()) {
            final var r = parse.apply(matcher.group(1));
            final var g = parse.apply(matcher.group(2));
            final var b = parse.apply(matcher.group(3));
            return new int[] { r, g, b };
        } else {
            System.err.println("'" + color + "'的格式非法默认为[255,0,0]");
            return defaultclr;
        } // if
    }

    /**
     * 颜色转rgb
     * 
     * @param clr 颜色对象
     * @return rgb的颜色数组
     */
    public static int[] clr2rgb(final Color clr) {
        return new int[] { clr.getRed(), clr.getGreen(), clr.getBlue() };
    }

    /**
     * 求一个数据的平方数
     * 
     * @param x 数值
     * @return 平方数
     */
    public static Double square(final Number x) {
        return x.doubleValue() * x.doubleValue();
    }

    /**
     * 求一个数据的立方数
     * 
     * @param x 数值
     * @return 立方数
     */
    public static Double cube(final Number x) {
        return x.doubleValue() * x.doubleValue() * x.doubleValue();
    }

    /**
     * 随机生成一个 [0,bound)之间的整数
     * 
     * @param bound 数量上边界
     * @return 随机整数
     */
    public static Integer rndint(final int bound) {
        return rand.nextInt(bound);
    }

    /**
     * 随机生成一个 [0,1)之间的 浮点数
     * 
     * @return 随机浮点数
     */
    public static Double rnddbl() {
        return rand.nextDouble();
    }

    public static boolean debug = false; // 调试标记
    public final static Random rand = new Random(); // 随机数对象
    public static final String DEFAULT_FORMAT_NAME = "jpg"; // 默认的图片格式

}
